/*
 * @copyright fxsen <br/>
 * @date 2022/3/31 22:24 <br/>
 * @email <a href="mailto:fxsen@foxmail.com">
 * @author fxsen
 */
package com.fxsen.commons.web.core.interceptor;

import com.fxsen.commons.cache.util.RedisUtils;
import com.fxsen.commons.core.annotation.NotTokenRequired;
import com.fxsen.commons.core.annotation.RequiredPermission;
import com.fxsen.commons.core.base.common.entity.JwtUser;
import com.fxsen.commons.core.exception.ForbiddenException;
import com.fxsen.commons.core.exception.NotAuthorizedException;
import com.fxsen.commons.sysmanager.core.entity.User;
import com.fxsen.commons.sysmanager.core.service.IPermissionService;
import com.fxsen.commons.sysmanager.core.service.IUserService;
import com.fxsen.commons.utils.CurrentTokenThreadLocalUtils;
import com.fxsen.commons.utils.JwtUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * Jwt 身份验证拦截器
 * @author fxsen
 * @date 2022/3/31 22:24
 * @email <a href="mailto:fxsen@foxmail.com">
 */
public class JwtAuthenticationInterceptor implements HandlerInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationInterceptor.class);
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private IUserService userService;
    @Autowired
    private IPermissionService permissionService;
    @Value("${project-config.account.isEnableRolePermissions:true}")
    private boolean isEnableRolePermissions;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
        // 预请求
        if (RequestMethod.OPTIONS.name().equals(request.getMethod())) {
            return true;
        }
        if ("/error".equals(request.getRequestURI())){
            return true;
        }
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //检查是否有NotTokenRequired注解
        if (method.isAnnotationPresent(NotTokenRequired.class)) {
            NotTokenRequired passToken = method.getAnnotation(NotTokenRequired.class);
            if (passToken.required()) {
                return true;
            }
        }
        // 从请求头中取出 token  这里需要和前端约定好把jwt放到请求头一个叫Authorization的地方
        String token = request.getHeader(JwtUtils.JWT_HEADER_KEY);
        //默认全部检查
        if (StringUtils.isBlank(token)){
            // 如果令牌为空, 再取Cookie中X-Token
            Cookie[] cookies = request.getCookies();
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if (JwtUtils.JWT_HEADER_KEY.equals(cookie.getName())) {
                        token = cookie.getValue();
                        break;
                    }
                }
            }
        }
        if (StringUtils.isBlank(token)){
            // 如果令牌为空, 再取QueryString中token
            token = request.getParameter(JwtUtils.JWT_HEADER_KEY);
        }
        if (StringUtils.isBlank(token)) {
            //这里其实是登录失效,没token了   这个错误也是我自定义的，需要自己修改
            throw new NotAuthorizedException("当前账号已过期，请重新登录~\uD83D\uDE0A");
        }
        JwtUtils.verifyToken(token);
        // 获取 token 中的 user
        JwtUser jwtUser = JwtUtils.getParamByToken(token);
        //如果redis存放的token过期，就重新登录
        /*if (!redisUtils.hasKey(JwtUtils.JWT_TOKEN_HEADER + "_" + userId)){
            throw new NotAuthorizedException("当前账号已过期，请重新登录~\uD83D\uDE0A");
        }*/
        //String redisToken = (String) redisUtils.get(JwtUtils.JWT_TOKEN_HEADER + "_" + userId);
        //当前token与redis存放的token不一致，返回重新登录。
        /*if (!token.equals(redisToken)){
            throw new NotAuthorizedException("当前账号已过期，请重新登录~\uD83D\uDE0A");
        }*/
        // 验证 token
        //找找看是否有这个user   因为我们需要检查用户是否存在，可以自行修改逻辑
        User user = userService.getById(jwtUser.getUserId());
        if (user == null) {
            //这个错误也是我自定义的
            throw new NotAuthorizedException("当前账号已过期，请重新登录~\uD83D\uDE0A");
        }
        CurrentTokenThreadLocalUtils.setValue(token);
        //是否有访问权限
        if (isEnableRolePermissions && !hasPermission(jwtUser.getUserId(),handlerMethod)){
            throw new ForbiddenException();
        }
        //存到redis，刷新token时间
        redisUtils.set(JwtUtils.JWT_HEADER_KEY+"_"+user.getId(),token,30*60);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }
    /**
     * 是否有权限
     */
    private boolean hasPermission(String userId,Object handler) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 获取方法上的注解
            RequiredPermission requiredPermission = handlerMethod.getMethod().getAnnotation(RequiredPermission.class);
            // 如果方法上的注解为空 则获取类的注解
            if (requiredPermission == null) {
                requiredPermission = handlerMethod.getMethod().getDeclaringClass().getAnnotation(RequiredPermission.class);
            }
            // 如果注解为null, 说明不需要拦截, 直接放过
            if (requiredPermission == null) {
                return true;
            }
            // 如果标记了注解，则判断权限
            return null != permissionService.findByTagAndUserId(requiredPermission.value(),userId);
        }
        return true;
    }
}
